﻿using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using System.IO;
using System.Linq;

namespace TinyAsset.Editor
{
    public class BuildPlayerTask : IBuildTask
    {
        BuildContext context;
        BuildSetting buildSetting;

        public bool Execute(BuildContext buildContext)
        {
            context = buildContext;
            buildSetting = buildContext.buildSetting;
            BuildRawAssets();
            BuildAssetBundles();
            CreateManifest();
            BuildPlayer();
            AssetDatabase.Refresh();
            return true;
        }

        void CreateManifest()
        {
            Manifest manifest = new Manifest();
            manifest.appVersion = context.AppVersion;
            manifest.resVersion = context.ResVersion;

            var assetBundles = context.assetBundles;

            List<string> dirs = new List<string>();
            List<BundledAssetKey> assets = new List<BundledAssetKey>();
            List<AssetBundleEntity> bundles = new List<AssetBundleEntity>();
            List<NativeAssetEntity> nativeAssets = new List<NativeAssetEntity>();
            Dictionary<string, FileEntityGroup> groups = new Dictionary<string, FileEntityGroup>();

            foreach (var node in assetBundles.Values)
            {
                string filename = Path.Combine(context.buildAssetBundlePath, node.AssetBundleName);
                using (FileStream fs = File.OpenRead(filename))
                {
                    AssetBundleEntity bundle = new AssetBundleEntity();

                    bundle.name = node.AssetBundleName;
                    bundle.hash = FileHelper.ComputeMd5Hash(fs);
                    bundle.size = fs.Length;
                    if (buildSetting.collectionSetting.buildinTags.Contains(node.TagName))
                        bundle.location = 0;
                    else
                        bundle.location = 1;
                    bundle.dependencies = new List<int>();
                    bundles.Add(bundle);
                }

                FileEntityGroup group;
                if (!groups.TryGetValue(node.TagName, out group))
                {
                    group = new FileEntityGroup()
                    {
                        name = node.TagName,
                        nativeAssetEntities = new List<int>(),
                        assetBundleEntities = new List<int>(),
                    };
                    groups.Add(group.name, group);
                }

                group.assetBundleEntities.Add(bundles.Count - 1);
            }

            foreach (var bundle in bundles)
            {
                var node = assetBundles[bundle.name];
                foreach (var bundleName in node.Children.Keys)
                    bundle.dependencies.Add(bundles.FindIndex(s => s.name == bundleName));
            }

            foreach (var item in context.nativeAssets)
            {
                string path = item.Key.Substring(7);
                string filePath = Path.Combine(context.buildResFolder, context.ResVersion.ToString(), path);
                using (FileStream fileStream = File.OpenRead(filePath))
                {
                    NativeAssetEntity nativeAsset = new NativeAssetEntity();

                    nativeAsset.name = path;
                    nativeAsset.size = fileStream.Length;
                    nativeAsset.hash = FileHelper.ComputeMd5Hash(fileStream);
                    if (buildSetting.collectionSetting.buildinTags.Contains(item.Value))
                        nativeAsset.location = 0;
                    else
                        nativeAsset.location = 1;

                    nativeAssets.Add(nativeAsset);
                }

                FileEntityGroup group;
                if (!groups.TryGetValue(item.Value, out group))
                {
                    group = new FileEntityGroup()
                    {
                        name = item.Value,
                        nativeAssetEntities = new List<int>(),
                        assetBundleEntities = new List<int>(),
                    };
                    groups.Add(group.name, group);
                }

                group.nativeAssetEntities.Add(nativeAssets.Count - 1);
            }

            foreach (var node in assetBundles.Values)
            {
                int bundleIndex = bundles.FindIndex(s => s.name.ToLower() == node.AssetBundleName);
                foreach (var assetName in node.Assets)
                {
                    BundledAssetKey assetRef = new BundledAssetKey();

                    string path = assetName.Substring(7);
                    string dir = path.Substring(0, path.LastIndexOf('/') + 1);
                    string name = path.Substring(path.LastIndexOf('/') + 1);

                    if (!dirs.Contains(dir))
                        dirs.Add(dir);

                    assetRef.name = name;
                    assetRef.dir = dirs.FindIndex(s => s == dir);
                    assetRef.bundle = bundleIndex;

                    assets.Add(assetRef);
                }
            }

            manifest.dirs = dirs.ToArray();
            manifest.bundledAssetKeys = assets.ToArray();
            manifest.assetBundleEntities = bundles.ToArray();
            manifest.nativeAssetEntities = nativeAssets.ToArray();
            manifest.fileEntityGroups = groups.Values.ToArray();

            using (FileStream fileStream = File.OpenWrite(context.ManifestLocation))
            {
                using (BinaryWriter writer = new BinaryWriter(fileStream))
                {
                    manifest.Serialize(writer);
                }
            }

            string destination = Path.Combine(context.buildResFolder, AssetBundleUtility.kManifest);
            FileHelper.Copy(context.ManifestLocation, destination);
        }

        void BuildRawAssets()
        {
            if (context.nativeAssets != null)
            {
                foreach (var item in context.nativeAssets)
                {
                    string p = item.Key.Substring(7);
                    string s = Path.Combine(Application.dataPath, p);
                    string d = Path.Combine(context.buildResFolder, context.ResVersion.ToString(), p);

                    FileHelper.Copy(s, d);
                }
            }
        }

        void BuildAssetBundles()
        {
            var assetBundleBuilds = new List<AssetBundleBuild>();
            if (context.assetBundles != null)
            {
                foreach (var node in context.assetBundles.Values)
                {
                    assetBundleBuilds.Add(new AssetBundleBuild
                    {
                        assetBundleName = node.AssetBundleName,
                        assetNames = node.Assets.ToArray(),
                    });
                }
                UnityEditor.BuildPipeline.BuildAssetBundles(context.buildAssetBundlePath, assetBundleBuilds.ToArray(), buildSetting.buildAssetBundleOptions, buildSetting.buildTarget);
            }
        }

        void BuildPlayer()
        {
            if (context.buildSetting.isBuildPlayer)
            {
                CopyStreamingAssets();

                List<string> scenes = new List<string>();
                foreach (var item in buildSetting.collectionSetting.buildinScenes)
                    scenes.Add(AssetDatabase.GetAssetPath(item));

                UnityEditor.BuildPipeline.BuildPlayer(scenes.ToArray(), context.PlayerLocation, buildSetting.buildTarget, buildSetting.buildOptions);
                buildSetting.buildHistoryRegistry.SetCurrentAppVersion(buildSetting.buildTarget, context.AppVersion);
            }
            buildSetting.buildHistoryRegistry.SetCurrentResVersion(buildSetting.buildTarget, context.ResVersion);
        }

        void CopyStreamingAssets()
        {
            foreach (var tag in buildSetting.collectionSetting.buildinTags)
            {
                var assetBundles = context.assetBundles;
                foreach (var node in assetBundles.Values)
                {
                    if (node.TagName == tag)
                    {
                        string origin = Path.Combine(context.buildAssetBundlePath, node.AssetBundleName);
                        string destination = Path.Combine(Application.streamingAssetsPath, AssetBundleUtility.kAssetBundle, node.AssetBundleName);

                        FileHelper.Copy(origin, destination);
                    }
                }

                foreach (var item in context.nativeAssets)
                {
                    if (item.Value == tag)
                    {
                        string path = item.Key.Substring(7);
                        string origin = Path.Combine(context.buildResFolder, context.ResVersion.ToString(), path);
                        string destination = Path.Combine(Application.streamingAssetsPath, path);

                        FileHelper.Copy(origin, destination);
                    }
                }
            }

            FileHelper.Copy(context.ManifestLocation, Path.Combine(Application.streamingAssetsPath, AssetBundleUtility.kManifest));
        }
    }
}